package com.behelpful.demo.search.event;

import cn.hutool.core.util.StrUtil;
import com.github.shyiko.mysql.binlog.event.*;
import com.behelpful.demo.search.constant.SearchConstants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;

/**
 * 负责分配 BinLog 事件到具体的处理类中的（管理者角色）
 *
 * @author Alay
 * @date 2021-08-05 00:26
 * @project behelpful
 */
@Slf4j
@Component
public class ListenerAllocate extends AbsBinLogEvent {

    @Override
    protected void init() {
    }

    /**
     * 一次MySQL的修改、插入、删除,会触发多次事件,会调用方法多次,注意处理好逻辑优化性能
     * 启动时：
     * 事件顺序：ROTATE、FORMAT_DESCRIPTION
     * </br>
     * 启动后：
     * 事件触发顺序:
     * 每次操作只触发一次(批量操作也只会触发一次)：ANONYMOUS_GTID,QUERY,
     * 每条记录row 触发一次独立的事件：TABLE_MAP(协大表库字段信息)、多选一：EXT_UPDATE_ROWS（执行更新）,EXT_DELETE_ROWS，EXT_WRITE_ROWS
     * 每次操作只触发一次(批量操作也只会触发一次) XID
     *
     * @param event
     */
    @Override
    public void onEvent(Event event) {
        EventType eventType = event.getHeader().getEventType();
        // 只处理我想要处理的事件
        if (eventType != EventType.QUERY
                && eventType != EventType.TABLE_MAP
                && eventType != EventType.EXT_UPDATE_ROWS
                && eventType != EventType.EXT_DELETE_ROWS
                && eventType != EventType.EXT_WRITE_ROWS) {
            return;
        }
        EventData eventData = event.getData();

        /**
         *	此事件操作的是修改表结构,修改后需要将缓存中存储的表结构删除
         * 	解析得出修改表结构的 sql 语句进行解析
         *	sql='ALTER TABLE `database_name(数据库的名称)`.`person_information`\r\n后面是具体的执行语句
         */
        if (EventType.QUERY == eventType) {
            QueryEventData queryEventData = (QueryEventData) eventData;
            String sql = queryEventData.getSql();
            // 增、删、改的事件，不做处理
            if (sql.startsWith(SearchConstants.EVENT_SQL_BEGIN)) {
                return;
            }
            // 修改表结构的事件
            String tableInfo = sql.substring(12);
            // 注意表名后面有一个空格需要去除
            tableInfo = StrUtil.subBefore(tableInfo, " \r\n", false);
            tableInfo = tableInfo.replace("`", "");
            String[] tableArr = tableInfo.split("\\.");
            // 移除缓存
            sqlSchemaColumnService.removeTableColumn(tableArr[0], tableArr[1]);
            return;
        }

        /**
         * 表结构映射事件
         */
        if (eventType == EventType.TABLE_MAP) {
            TableMapEventData mapEventData = (TableMapEventData) eventData;
            long tableId = mapEventData.getTableId();
            // 获取头文件中存储的 tableId  <---> tableName 的映射
            String tableFullName = tableFullNameMap.get(tableId);

            if (null != tableFullName) {
                // 此表已经不是第一次触发该事件了，不需要重复的处理做准备的工作
                return;
            }
            if (null == tableFullName) {
                String tableName = mapEventData.getTable();
                // 此事件不是我要处理的数据库表的日志事件,不在我的事件实例中,不做处理
                if (null == eventInstanceMap.get(tableName)) {
                    return;
                }
                String database = mapEventData.getDatabase();
                tableFullName = database + "." + tableName;
                tableFullNameMap.put(tableId, tableFullName);
                return;
            }
        }

        /**
         * 触发的事件是具体操作的事件
         */
        if (eventType == EventType.EXT_UPDATE_ROWS
                || eventType == EventType.EXT_WRITE_ROWS
                || eventType == EventType.EXT_DELETE_ROWS) {
            try {
                // 通过反射获取到事件对象数据的 tableId的值
                Field tableIdField = eventData.getClass().getDeclaredField("tableId");
                tableIdField.setAccessible(true);
                long tableId = (long) tableIdField.get(eventData);

                String tableFullName = tableFullNameMap.get(tableId);
                // 不是我想要监听的数据库表的的事件,不做处理
                if (null == tableFullName) {
                    return;
                }
                // 通过表名 获得具体是时间处理类对象 tableName <----> eventInstance
                String[] tableNameArr = tableFullName.split("\\.");
                AbsBinLogEvent eventInstance = eventInstanceMap.get(tableNameArr[1]);
                // 调用具体的事件逻辑处理
                eventInstance.onEvent(event);
            } catch (NoSuchFieldException | IllegalAccessException e) {
                log.error("执行 BinLog 同步数据失败,时间类型{},原因：{},Msg:{}", eventType.toString(), e.getCause(), e.getMessage());
            }
        }
    }
}
